package org.springframework.boot.netty.handler;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.ReferenceCountUtil;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.boot.netty.converter.JsonMessageConverter;
import org.springframework.boot.netty.converter.MessageConverter;
import org.springframework.boot.netty.exception.ConvertException;
import org.springframework.boot.netty.filter.JwtSecurityFilter;
import org.springframework.boot.netty.handler.common.AbstractChannelInboundHandlerAdapter;
import org.springframework.boot.netty.listener.Message;
import org.springframework.boot.netty.support.CheckFileType;
import org.springframework.boot.netty.support.JsonUtil;
import org.springframework.boot.netty.webSocketHeart.PingPong;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;

import java.io.*;
import java.util.Map;

/**
 * 
 * @Description: 处理消息的handler
 * TextWebSocketFrame： 在netty中，是用于为websocket专门处理文本的对象，frame是消息的载体
 */

/**
 * handlerAdded() 当检测到新连接之后，调用 ch.pipeline().addLast(new XXXHandler()); 之后的回调
 * channelRegistered() 当前的 channel 的所有的逻辑处理已经和某个 NIO 线程建立了绑定关系
 * channelActive() channel 的 pipeline 中已经添加完所有的 handler，并且绑定好一个 NIO 线程之后，这条连接算是真正激活了，接下来就会回调到此方法。
 * channelRead() 收到发来的数据，每次都会回调此方法，表示有数据可读。
 * channelReadComplete() 数据读取完毕回调此方法
 * channelInactive()  表示这条连接已经被关闭了，这条连接在 TCP 层面已经不再是 ESTABLISH 状态了
 * channelUnregistered()  表示与这条连接对应的 NIO 线程移除掉对这条连接的处理
 * handlerRemoved() 这条连接上添加的所有的业务逻辑处理器都被移除掉后调用
 */
public class DefaultWebSocketHandlerAdapter extends AbstractChannelInboundHandlerAdapter {


	private final static Logger logger = LoggerFactory.getLogger(DefaultWebSocketHandlerAdapter.class);

	private MessageConverter messageConverter;

	private JwtSecurityFilter jwtSecurityFilter;

	public DefaultWebSocketHandlerAdapter() {
	}


	public DefaultWebSocketHandlerAdapter(ApplicationContext applicationContext) {

		this.applicationContext = applicationContext;

		for (MessageConverter messageConverter : this.messageConverters) {
			if(messageConverter.fromSupport(String.class,String.class)){
				this.messageConverter = messageConverter;
				break;
			}
		}
	}

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg)
			throws Exception {

		if(msg instanceof TextWebSocketFrame){
			try {
				// 获取客户端传输过来的消息
				TextWebSocketFrame textWebSocketFrame = (TextWebSocketFrame)msg;
				String content = this.messageConverter.fromMessage(textWebSocketFrame.text(),String.class);
				// 心跳消息
				PingPong pingPong = JsonUtil.string2Obj(content, PingPong.class);
				ctx.fireChannelRead(pingPong);
			}catch (ConvertException e){
				logger.debug("业务消息处理中......");
			}
		}

		if(msg instanceof WebSocketFrame) {
			handleWebSocketFrame(ctx, (WebSocketFrame) msg);
		}

		// 释放内存
		ReferenceCountUtil.release(msg);
	}

	private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception {

		// 业务消息
		if (frame instanceof TextWebSocketFrame) {

			TextWebSocketFrame msg = (TextWebSocketFrame)frame;
			// 获取客户端传输过来的消息
			String content = this.messageConverter.fromMessage(msg.text(),String.class);

			//如果启用安全机制则校验
			if(applicationContext.containsBean("shiroConfig")){
				Map<String, Object> jsonMap = JsonUtil.string2Map(content);
				String accessToken = (String) jsonMap.getOrDefault("access_token", "");
				jsonMap.remove("access_token");
				content = JsonUtil.obj2StringPretty(jsonMap);
				Message<String> message = new Message<>();
				message.setAccessToken(accessToken);
				message.setContent(content);
				super.channelRead(ctx,message);
			}else {
				super.channelRead(ctx,content);
			}

			if(logger.isDebugEnabled()){
				org.springframework.boot.autoconfigure.netty.channel.Channel.UserChannelRel.output();
				logger.debug("receive message is :{}",msg);
			}
			return;
		}
		if (frame instanceof PingWebSocketFrame) {
			// 响应客户端ping
			ctx.writeAndFlush(new PongWebSocketFrame(frame.content().retain()));
			return;
		}
		if (frame instanceof CloseWebSocketFrame) {
			ctx.writeAndFlush(frame.retainedDuplicate()).addListener(ChannelFutureListener.CLOSE);
			return;
		}
		if (frame instanceof BinaryWebSocketFrame) {
			// 文件的传输
			logger.info("服务器接收到二进制消息,消息长度:[{}]", frame.content().capacity());
			ByteBuf byteBuf = Unpooled.directBuffer(frame.content().capacity());
			byte[] array = byteBuf.array();
			InputStream sbs = new ByteArrayInputStream(array);
			String fileType = CheckFileType.getFileType(sbs);
			File tmpFile = new File(String.format("a.%s", fileType));
			FileUtils.writeByteArrayToFile(tmpFile,array);
			byteBuf.release();
// 压入队列，向容器中发布事件,缓存客户端id与Channel
			super.channelRead(ctx,tmpFile);
			return;
		}
		if (frame instanceof PongWebSocketFrame) {
			return;
		}
	}

	public void afterPropertiesSet(ApplicationEventPublisher applicationEventPublisher){
		super.afterPropertiesSet(applicationEventPublisher);
		this.messageConverter = this.applicationContext.getBean("jsonMessageConverter", JsonMessageConverter.class);
		try {
			this.jwtSecurityFilter = this.applicationContext.getBean(JwtSecurityFilter.class);
		}catch (BeansException e){
			logger.debug(e.getMessage());
		}
	}


}
